

# 导入 uuid 模块，用于生成唯一标识符
import uuid
# 从 qtpy 导入 QtWidgets 模块，提供各种 GUI 组件
from qtpy import QtWidgets
# 从 qtpy 导入 QtGui 和 QtCore 模块，提供图形和核心功能
from qtpy import QtGui, QtCore

# 从 src 的 UI 工具模块导入可编辑样式表
from src.UI.Utils.stylesheet import editableStyleSheet


class ToolBase(object):
    """
    所有编辑器工具的基类。

    .. py:method:: name()
        :staticmethod:

        返回此工具的名称。
    """

    # 工具所属的包名
    packageName = ""

    def __init__(self):
        """
        初始化工具基类。
        生成唯一标识符，并将 PyFlow 实例初始化为 None。
        """
        super(ToolBase, self).__init__()
        # 生成唯一标识符
        self.uid = uuid.uuid4()
        # 存储 PyFlow 实例的引用
        self.pyFlowInstance = None

    @staticmethod
    def supportedSoftwares():
        """
        返回工具支持运行的软件列表。

        :return: 支持的软件名称列表，默认为 ["any"]
        """
        return ["any"]

    def onShow(self):
        """
        当工具弹出时调用。
        基类中此方法为空，子类可重写以实现特定逻辑。
        """
        pass

    def onDestroy(self):
        """
        当工具销毁时调用。
        基类中此方法为空，子类可重写以实现特定逻辑。
        """
        pass

    def saveState(self, settings):
        """
        工具保存时调用。
        当调用此方法时，已选择正确的设置组，只需调用 **setValue** 方法保存数据。

        .. code-block:: python
            :linenos:

            def saveState(self, settings):
                super(ScreenshotTool, self).saveState(settings)
                settings.setValue("format", self.format)

        :param settings: 设置类实例
        :type settings: :class:`QSettings`
        """
        # 保存工具的唯一标识符
        settings.setValue("uid", str(self.uid))

    def restoreState(self, settings):
        """
        应用加载时调用。
        在此方法中恢复任何保存的状态。与 **saveState** 类似，设置组已选中，可调用 **value** 方法访问数据。

        .. code-block::
            :linenos:

            def restoreState(self, settings):
                super(ScreenshotTool, self).restoreState(settings)
                formatValue = settings.value("format")
                if formatValue is not None:
                    self.format = formatValue
                else:
                    self.format = "PNG"

        :param settings: 设置类实例
        :type settings: :class:`QSettings`
        """
        # 获取保存的唯一标识符
        uidStr = settings.value("uid")
        if uidStr is not None:
            # 若存在，则将其转换为 UUID 对象
            self.uid = uuid.UUID(uidStr)
        else:
            # 若不存在，则重新生成一个 UUID
            self.uid = uuid.uuid4()

    def setAppInstance(self, pyFlowInstance):
        """
        设置 PyFlow 实例。
        仅在实例未设置时进行设置。

        :param pyFlowInstance: PyFlow 应用实例
        """
        if self.pyFlowInstance is None:
            self.pyFlowInstance = pyFlowInstance

    @staticmethod
    def toolTip():
        """
        返回工具的提示信息。

        :rtype: str
        :return: 默认的工具提示信息
        """
        return "Default tooltip"

    def uniqueName(self):
        """
        返回工具的唯一名称。
        对于非单例工具，需要为每个实例存储单独的数据，使用唯一标识符 (:class:`~uuid.UUID`) 作为后缀。

        :rtype: str
        :return: 工具的唯一名称
        """
        return "{0}::{1}".format(self.name(), str(self.uid))

    @staticmethod
    def name():
        """
        返回工具的名称。

        :return: 工具的名称，默认为 "ToolBase"
        """
        return "ToolBase"


class ShelfTool(ToolBase):
    """
    工具栏工具的基类。
    """

    def __init__(self):
        """
        初始化工具栏工具。
        调用父类的构造函数。
        """
        super(ShelfTool, self).__init__()

    def contextMenuBuilder(self):
        """
        构建上下文菜单。
        基类中返回 None，子类可重写以构建自定义上下文菜单。

        :return: 上下文菜单对象，默认为 None
        """
        return None

    @staticmethod
    def getIcon():
        """
        返回工具的图标。

        :return: 工具的图标，默认为系统主题中的 "go-home" 图标
        """
        return QtGui.QIcon.fromTheme("go-home")

    def do(self):
        """
        执行工具的主要操作。
        目前存在问题，`self.canvas` 尚未定义。

        :raises: 未来可能会因 `self.canvas` 未定义而报错
        """
        print(self.name(), "called!", self.canvas)  # TODO: there is no 'canvas' yet


class FormTool(QtWidgets.QMdiSubWindow, ToolBase):
    """
    表单工具的基类。
    继承自 `QtWidgets.QMdiSubWindow` 和 `ToolBase`。
    """

    def __init__(self):
        """
        初始化表单工具。
        分别调用 `ToolBase` 和 `QtWidgets.QMdiSubWindow` 的构造函数，设置工具提示和对象名称。
        """
        ToolBase.__init__(self)
        QtWidgets.QMdiSubWindow.__init__(self)
        # 设置工具提示
        self.setToolTip(self.toolTip())
        # 设置对象的唯一名称
        self.setObjectName(self.uniqueName())

    @staticmethod
    def supportedSoftwares():
        """
        返回表单工具支持运行的软件列表。

        :return: 支持的软件名称列表，默认为 ["any"]
        """
        return ["any"]

    @staticmethod
    def isSingleton():
        """
        判断该工具是否为单例。

        :return: 是否为单例，默认为 False
        """
        return False

    def onShow(self):
        """
        当表单工具弹出时调用。
        调用父类的 `onShow` 方法，并设置窗口标题为工具名称。
        """
        super(FormTool, self).onShow()
        # 设置窗口标题
        self.setWindowTitle(self.name())

    def contextMenuBuilder(self):
        """
        构建表单工具的上下文菜单。
        基类中返回 None，子类可重写以构建自定义上下文菜单。

        :return: 上下文菜单对象，默认为 None
        """
        return None

    @staticmethod
    def getIcon():
        """
        返回表单工具的图标。

        :return: 图标对象，默认为 None
        """
        return None

    def restoreState(self, settings):
        """
        恢复表单工具的状态。
        调用父类的 `restoreState` 方法，并更新对象的唯一名称。

        :param settings: 设置类实例
        :type settings: :class:`QSettings`
        """
        super(FormTool, self).restoreState(settings)
        # 更新对象的唯一名称
        self.setObjectName(self.uniqueName())

    def closeEvent(self, event):
        """
        处理表单工具的关闭事件。
        调用 `onDestroy` 方法，通知父对象注销工具实例，并接受关闭事件。

        :param event: 关闭事件对象
        """
        self.onDestroy()
        self.parent().unregisterToolInstance(self)
        event.accept()

    def do(self):
        """
        执行表单工具的主要操作。
        目前存在问题，`self.canvas` 尚未定义。

        :raises: 未来可能会因 `self.canvas` 未定义而报错
        """
        print(self.name(), "called!", self.canvas)


class DockTool(QtWidgets.QDockWidget, ToolBase):
    """
    停靠工具的基类。
    继承自 `QtWidgets.QDockWidget` 和 `ToolBase`。
    """

    def __init__(self):
        """
        初始化停靠工具。
        分别调用 `ToolBase` 和 `QtWidgets.QDockWidget` 的构造函数，设置工具提示、允许停靠的区域、对象名称等。
        """
        ToolBase.__init__(self)
        QtWidgets.QDockWidget.__init__(self)
        # 设置工具提示
        self.setToolTip(self.toolTip())
        # 设置允许停靠的区域
        self.setAllowedAreas(
            QtCore.Qt.BottomDockWidgetArea
            | QtCore.Qt.LeftDockWidgetArea
            | QtCore.Qt.RightDockWidgetArea
            | QtCore.Qt.TopDockWidgetArea
        )
        # 设置对象的唯一名称
        self.setObjectName(self.uniqueName())
        # 设置停靠窗口的标题栏部件
        self.setTitleBarWidget(DockTitleBar(self))
        # 设置初始状态为非浮动
        self.setFloating(False)

    @staticmethod
    def defaultDockArea():
        """
        返回停靠工具的默认停靠区域。

        :return: 默认停靠区域，默认为 `QtCore.Qt.LeftDockWidgetArea`
        """
        return QtCore.Qt.LeftDockWidgetArea

    @staticmethod
    def isSingleton():
        """
        判断该停靠工具是否为单例。

        :return: 是否为单例，默认为 False
        """
        return False

    def onShow(self):
        """
        当停靠工具弹出时调用。
        调用父类的 `onShow` 方法，并设置窗口标题为工具名称。
        """
        super(DockTool, self).onShow()
        # 设置窗口标题
        self.setWindowTitle(self.name())

    @staticmethod
    def getIcon():
        """
        返回停靠工具的图标。

        :return: 图标对象，默认为 None
        """
        return None

    def restoreState(self, settings):
        """
        恢复停靠工具的状态。
        调用父类的 `restoreState` 方法，并更新对象的唯一名称。

        :param settings: 设置类实例
        :type settings: :class:`QSettings`
        """
        super(DockTool, self).restoreState(settings)
        # 更新对象的唯一名称
        self.setObjectName(self.uniqueName())

    def closeEvent(self, event):
        """
        处理停靠工具的关闭事件。
        调用 `onDestroy` 方法，通知父对象注销工具实例，并接受关闭事件。

        :param event: 关闭事件对象
        """
        self.onDestroy()
        self.parent().unregisterToolInstance(self)
        event.accept()

    def addButton(self, button):
        """
        向停靠工具的标题栏添加按钮。

        :param button: 要添加的按钮对象
        """
        self.titleBarWidget().addButton(button)


class DockTitleBar(QtWidgets.QWidget):
    """
    停靠窗口的标题栏类。
    自定义停靠窗口的标题栏外观和行为。
    """

    def __init__(self, dockWidget, renamable=False):
        """
        初始化停靠窗口的标题栏。

        :param dockWidget: 关联的停靠窗口对象
        :param renamable: 标题是否可重命名，默认为 False
        """
        super(DockTitleBar, self).__init__(dockWidget)
        # 设置标题是否可重命名
        self.renamable = renamable
        # 设置布局
        self.setLayout(QtWidgets.QHBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 1)
        # 创建按钮布局
        self.buttonsLay = QtWidgets.QHBoxLayout()
        self.buttonsLay.setSpacing(1)
        self.buttonsLay.setContentsMargins(1, 1, 1, 1)
        # 创建一个分组框
        self.box = QtWidgets.QGroupBox("")
        self.box.setLayout(self.buttonsLay)
        self.box.setObjectName("Docked")
        # 将分组框添加到主布局中
        self.layout().addWidget(self.box)

        # 将分组框的鼠标事件重定向到标题栏
        self.box.mouseDoubleClickEvent = self.mouseDoubleClickEvent
        self.box.mousePressEvent = self.mousePressEvent
        self.box.mouseMoveEvent = self.mouseMoveEvent
        self.box.mouseReleaseEvent = self.mouseReleaseEvent

        # 创建标题标签
        self.titleLabel = QtWidgets.QLabel(self)
        self.titleLabel.setStyleSheet("background:transparent")
        # 创建标题编辑框
        self.titleEdit = QtWidgets.QLineEdit(self)
        self.titleEdit.hide()
        # 连接编辑完成信号到处理函数
        self.titleEdit.editingFinished.connect(self.finishEdit)

        # 设置按钮的大小
        self.buttonSize = QtCore.QSize(14, 14)

        # 创建停靠按钮
        self.dockButton = QtWidgets.QToolButton(self)
        self.dockButton.setIcon(QtGui.QIcon(":/split_window.png"))
        self.dockButton.setMaximumSize(self.buttonSize)
        self.dockButton.setAutoRaise(True)
        # 连接按钮点击信号到切换浮动状态的函数
        self.dockButton.clicked.connect(self.toggleFloating)

        # 创建关闭按钮
        self.closeButton = QtWidgets.QToolButton(self)
        self.closeButton.setMaximumSize(self.buttonSize)
        self.closeButton.setAutoRaise(True)
        self.closeButton.setIcon(QtGui.QIcon(":/close_window.png"))
        # 连接按钮点击信号到关闭父窗口的函数
        self.closeButton.clicked.connect(self.closeParent)

        # 在按钮布局中添加间距和部件
        self.buttonsLay.addSpacing(2)
        self.buttonsLay.addWidget(self.titleLabel)
        self.buttonsLay.addWidget(self.titleEdit)
        self.buttonsLay.addStretch()
        self.buttonsLay.addSpacing(5)
        self.buttonsLay.addWidget(self.dockButton)
        self.buttonsLay.addWidget(self.closeButton)

        # 连接停靠窗口功能改变信号到处理函数
        dockWidget.featuresChanged.connect(self.onFeaturesChanged)

        # 处理初始的停靠窗口功能
        self.onFeaturesChanged(dockWidget.features())
        # 设置标题
        self.setTitle(dockWidget.windowTitle())
        # 安装事件过滤器
        dockWidget.installEventFilter(self)
        # 连接停靠窗口浮动状态改变信号到处理函数
        dockWidget.topLevelChanged.connect(self.ChangeFloatingStyle)

    def eventFilter(self, source, event):
        """
        事件过滤器，处理停靠窗口的标题改变事件。

        :param source: 事件源对象
        :param event: 事件对象
        :return: 调用父类的事件过滤器返回结果
        """
        if event.type() == QtCore.QEvent.WindowTitleChange:
            # 当窗口标题改变时，更新标题栏的标题
            self.setTitle(source.windowTitle())
        return super(DockTitleBar, self).eventFilter(source, event)

    def startEdit(self):
        """
        开始编辑标题。
        隐藏标题标签，显示标题编辑框，并设置焦点。
        """
        self.titleLabel.hide()
        self.titleEdit.show()
        self.titleEdit.setFocus()

    def finishEdit(self):
        """
        完成标题编辑。
        隐藏标题编辑框，显示标题标签，并更新停靠窗口的标题。
        """
        self.titleEdit.hide()
        self.titleLabel.show()
        self.parent().setWindowTitle(self.titleEdit.text())

    def onFeaturesChanged(self, features):
        """
        处理停靠窗口功能改变事件。
        根据停靠窗口的功能显示或隐藏关闭按钮和停靠按钮。

        :param features: 停靠窗口的功能标志
        :raises ValueError: 当启用垂直标题栏时抛出异常，因为不支持
        """
        if not features & QtWidgets.QDockWidget.DockWidgetVerticalTitleBar:
            # 根据停靠窗口是否可关闭显示或隐藏关闭按钮
            self.closeButton.setVisible(
                features & QtWidgets.QDockWidget.DockWidgetClosable == QtWidgets.QDockWidget.DockWidgetClosable)
            # 根据停靠窗口是否可浮动显示或隐藏停靠按钮
            self.dockButton.setVisible(
                features & QtWidgets.QDockWidget.DockWidgetFloatable == QtWidgets.QDockWidget.DockWidgetFloatable)
        else:
            # 不支持垂直标题栏
            raise ValueError("vertical title bar not supported")

    def setTitle(self, title):
        """
        设置标题栏的标题。
        更新标题标签和标题编辑框的文本。

        :param title: 新的标题文本
        """
        self.titleLabel.setText(title)
        self.titleEdit.setText(title)

    def ChangeFloatingStyle(self, state):
        """
        根据停靠窗口的浮动状态改变标题栏的样式。

        :param state: 是否为浮动状态
        """
        if not state:
            # 非浮动状态，使用可编辑样式表
            self.box.setStyleSheet(editableStyleSheet().getStyleSheet())
        else:
            # 浮动状态，使用渐变背景样式
            self.box.setStyleSheet(
                """QGroupBox{
                                background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                stop: 0 %s,
                                stop: 0.6 %s,
                                stop: 1.0 %s);}"""
                % (
                    "rgba%s" % str(editableStyleSheet().ButtonsColor.getRgb()),
                    "rgba%s" % str(editableStyleSheet().BgColorBright.getRgb()),
                    "rgba%s" % str(editableStyleSheet().BgColorBright.getRgb()),
                )
            )

    def update(self, *args, **kwargs):
        """
        更新标题栏状态。
        根据停靠窗口的浮动状态改变样式，并调用父类的 `update` 方法。

        :param args: 位置参数
        :param kwargs: 关键字参数
        """
        self.ChangeFloatingStyle(self.parent().isFloating())
        super(DockTitleBar, self).update(*args, **kwargs)

    def toggleFloating(self):
        """
        切换停靠窗口的浮动状态。
        """
        self.parent().setFloating(not self.parent().isFloating())

    def closeParent(self):
        """
        关闭父停靠窗口。
        取消切换视图动作的选中状态，并关闭父窗口。
        """
        self.parent().toggleViewAction().setChecked(False)
        self.parent().close()

    def mouseDoubleClickEvent(self, event):
        """
        处理鼠标双击事件。
        如果双击位置在标题标签范围内且标题可重命名，则开始编辑标题；否则调用父类的处理方法。

        :param event: 鼠标双击事件对象
        """
        if event.pos().x() <= self.titleLabel.width() and self.renamable:
            self.startEdit()
        else:
            # 保持正常的双击行为
            super(DockTitleBar, self).mouseDoubleClickEvent(event)

    def mouseReleaseEvent(self, event):
        """
        处理鼠标释放事件。
        忽略该事件。

        :param event: 鼠标释放事件对象
        """
        event.ignore()

    def mousePressEvent(self, event):
        """
        处理鼠标按下事件。
        忽略该事件。

        :param event: 鼠标按下事件对象
        """
        event.ignore()

    def mouseMoveEvent(self, event):
        """
        处理鼠标移动事件。
        忽略该事件。

        :param event: 鼠标移动事件对象
        """
        event.ignore()

    def addButton(self, button):
        """
        向标题栏添加按钮。
        设置按钮的自动提升属性和最大尺寸，并插入到按钮布局中。

        :param button: 要添加的按钮对象
        """
        button.setAutoRaise(True)
        button.setMaximumSize(self.buttonSize)
        self.buttonsLay.insertWidget(5, button)